#!/usr/bin/python2.4
# jhebert@cs.washington.edu


import time
import threading
import chartParser3
import chartNode
import workerPool


class ChartRunner:
  """ 8/14/06
      1/10/07
   This is the seventh attempt at chartRunner.
   It rewrites and compiles python programs in a datflow manner.
   This program now adminsters charts only.
   ChartNodes handle charts as computational nodes. """

  def __init__(self, name, logs):
    """ The chart parsed handles the parsing of programs and charts. """
    statusLog, commandLog = logs
    self.nodeName=name
    self.chartParser = chartParser3.ChartParser(statusLog, self)
    self.charts=[]
    self.recentlyLoadedCharts=[]
    self.systemCharts = {}
    self.threadPools=[]
    self.statusLog=statusLog
    self.commandLog=commandLog
    self.mutex = threading.RLock()
    statusLog.put((self.nodeName,'Done initing',self))
    

  def Run(self):
    """ Signal each of the charts under this runner to begin computing. """
    for chart in self.recentlyLoadedCharts:
      self.statusLog.put((self.nodeName, "Running", chart))
      self.__RunChart(chart)
    self.recentlyLoadedCharts=[]

  def Stop(self):
    """ Signal  each threadPool to finish working and stop.
    This method will attempt to wait for any current computation
    to finish before the threadPools stop. """
    for chart in self.charts:
      self.statusLog.put((self.nodeName, "Stopping", chart))
      chart.Emit(chart.nodeName, 'exit', 1)
    for p in self.threadPools:
      p.Stop()
    time.sleep(.5)

  def LoadChart(self, name, args):
    self.statusLog.put((self.nodeName, "Loading", name))
    toReturn = self.__Load(name, args)
    self.UpdateInfo()
    return toReturn

  def __Load(self, name, args):
    """ This loads a chart for this specific chartRunner. """
    #TODO: Threads should not be allocated at this level.
    #      we want threads to run per program for ease, but
    #      how many threads can we be allocating in total?
    pool = workerPool.WorkerPool(5)
    self.mutex.acquire()
    self.threadPools.append(pool)
    self.mutex.release()
    chartLog=self.statusLog.SetChartName(name)
    c = chartNode.ChartNode(pool, name, chartLog, self)
    result,msg=self.chartParser.ParseChartFromFile(c, name)
    if(not result):
      return (False, msg)
    for arg in args:
      argName, argValue = arg
      c.Emit(c.nodeName, argName, argValue)
    self.mutex.acquire()
    self.charts.append(c)
    self.recentlyLoadedCharts.append(c)
    self.mutex.release()
    return (True,'')

  def __RunChart(self, chart):
    """ Signal a specific chart to start running by
    emitting the special start value to the main chart. """
    chart.Emit(chart.nodeName, 'start', 1)
    while(chart.ReadyToRun()):
      chart.Run()
      time.sleep(.1)

  def Emit(self, caller, varName, varValue):
    """ This catches the top level emits from a chart for access
    and use by the chartManager. """
    if(varName=='_ERROR_'):
      self.commandLog.put((self.nodeName, varName, varValue))
    elif(varName=='_PRINT_'):
      self.commandLog.put((self.nodeName, varName, varValue))
    elif(varName=='_LOAD_'):
      self.commandLog.put((self.nodeName, varName, varValue))
    else:
      toPut=["Top level emit of:",varName,"=",varValue,"from",caller]
      text=''.join([str(a) for a in toPut])
      self.statusLog.put((self.nodeName, text, self))

  def AssembleInfoPage(self):
    toReturn=(
      '<b>Name:</b> %(name)s\n'
      '<b>Charts:</b> %(charts)s\n') % ({
      'name' : self.nodeName,
      'charts' : ', '.join([a.nodeName for a in self.charts]), })
    return toReturn

  def UpdateInfo(self):
    data=self.AssembleInfoPage()
    self.statusLog.put((self.nodeName,'info',data))
